/***************************************************************************
 *   Copyright (C) 2009 by Geoffrey McRae   *
 *   geoff@spacevs.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <math.h>

#include "mediaplayer.h"
#include "common/player.h"

inline bool mp_stream_isrunning(mp_stream_t *s) {
	bool ret;
	pthread_rwlock_wrlock(&s->lRunning);
	ret = s->bRunning;
	pthread_rwlock_unlock(&s->lRunning);
	return ret;
}

void mp_stream_audio(mp_stream_t *s) {
	AVPacket	*pkt;
	int8_t		buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
//	DECLARE_ALIGNED(16, int8_t, buffer[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2]);
	int		len, pkt_size, data_size;
	unsigned int	frames;
	uint8_t 	*pkt_data;
	bool		bDead;
	double		pts;

	bDead = false;
	if (!s->rndr->audio_open(s->rndr_data, s->pStream->codec->sample_rate, s->pStream->codec->channels))
		return;

	for(;;) {

		pkt = ll_shift(s->lPackets);
		if ((!mp_stream_isrunning(s)) || (!pkt)) {
			ll_empty(s->lPackets);
			break;
		}

		pkt_data = pkt->data;
		pkt_size = pkt->size;

		while(pkt_size > 0) {
			data_size = sizeof(buffer);
			if ((len = avcodec_decode_audio2(s->pStream->codec, (int16_t*)buffer, &data_size, pkt_data, pkt_size)) < 0) {
				pkt_size = 0;
				break;
			}

			pkt_data += len;
			pkt_size -= len;

			/* No data */
			if (data_size <= 0) continue;

			frames  = data_size / (sizeof(int16_t) * s->pStream->codec->channels);
			if (!s->bPrimed) {
				s->rndr->audio_pause(s->rndr_data);
				mp_stream_primed(s);
				s->rndr->audio_resume(s->rndr_data);
			}

			/* If we are a sync source, tell the master clock we have been updated */
			if (s->bSyncSource) {
				pts = pkt->pts * av_q2d(s->pStream->time_base);

				/* Set the pts and signal the clock */
				pthread_mutex_lock(&s->p->mClock);
				s->dClock		= pts - s->rndr->audio_getdelay(s->rndr_data);
				s->bClockUpdated	= true;
				pthread_cond_signal (&s->p->cClock);
				pthread_mutex_unlock(&s->p->mClock);
			}

			if (!s->rndr->audio_write(s->rndr_data, (int16_t*)buffer, frames)) {
				ERR("%s", "Unable to write audio");
				bDead = true;
				break;
			}
		}

		mp_av_free_packet(pkt);
		if (bDead) break;
	}

	s->rndr->audio_close(s->rndr_data);
}

/* This function decodes the video and puts it into the event queue */
void mp_stream_video(mp_stream_t *s) {
	AVPacket	*pkt;
	int		len, i;
	int		frameFinished;
	double		pts, last;
	mp_queue_t	*q;
	double		aspect_ratio;
	int		width, height, queue_val;
	bool		bHadPacket;

	/* Calculate the aspect ratio */
	#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(21<<8)+0)
	if (s->pStream->sample_aspect_ratio.num)	aspect_ratio = av_q2d(s->pStream->sample_aspect_ratio);
	else
	#endif
	if (s->pStream->codec->sample_aspect_ratio.num)	aspect_ratio = av_q2d(s->pStream->codec->sample_aspect_ratio);
	else						aspect_ratio = 1;

	if (aspect_ratio <= 0.0) aspect_ratio = 1;
	aspect_ratio *= (double)s->pStream->codec->width / (double)s->pStream->codec->height;

	/* Calculate the display width and height */
	height	= s->pStream->codec->height;
	width	= (int)rint((double)height * aspect_ratio) & ~1;
	if (width > s->pStream->codec->width) {
		width	= s->pStream->codec->width;
		height	= (int)rint((double)width / aspect_ratio) & ~1;
	}

	INF("Aspect Ratio: %f", aspect_ratio);
	INF("Frame   Size: %dx%d", s->pStream->codec->width, s->pStream->codec->height);
	INF("Display Size: %dx%d", width                   , height                   );

	if (!s->rndr->video_open(
		s->rndr_data,
		width,
		height,
		s->pStream->codec->width,
		s->pStream->codec->height,
		s->pStream->codec->pix_fmt
	))	return;

	/* Let the renderer initialize the storage for the frames */
	INF("%s", "Telling the renderer to initialize it's buffers");
	for(i = 0; i < s->iQueueSize; ++i) {
		q = ll_next(s->lQueue);
		q->pFrame = avcodec_alloc_frame();
		s->rndr->video_queue_new(s->rndr_data, q->pFrame, &q->storage);
	}

	q		= ll_next(s->lQueue);
	last		= 0;
	bHadPacket	= false;

	for(;;) {
		//if we are not primed, and we have no more packets
		//then signal that we are primed
		if ((bHadPacket) && (!s->bPrimed) && (ll_isempty(s->lPackets)))
			mp_stream_primed(s);

		pkt = ll_shift(s->lPackets);
		if ((!mp_stream_isrunning(s)) || (!pkt)) {
			ll_empty(s->lPackets);
			break;
		}
		bHadPacket = true;

		s->pStream->codec->reordered_opaque = pkt->pts;
		len = avcodec_decode_video(s->pStream->codec, q->pFrame, &frameFinished, pkt->data, pkt->size);
		if (!frameFinished) {
			mp_av_free_packet(pkt);
			continue;
		}

		if (q->pFrame->reordered_opaque)
			pts = q->pFrame->reordered_opaque;
		else	pts = AV_NOPTS_VALUE;

		pts *= av_q2d(s->pStream->time_base);
		mp_av_free_packet(pkt);

		/* queue the frame */
		s->rndr->video_queue(s->rndr_data, q->pFrame, &q->storage);
		mp_add_event(s->p, MP_EVENT_PTS_VALUE, pts, mp_stream_video_update, q);
		q = ll_next(s->lQueue);

		if (!s->bPrimed) {
			sem_getvalue(&s->sQueue, &queue_val);
			if (queue_val == 0) mp_stream_primed(s);
		}
		sem_wait(&s->sQueue);
	}

	/* Wait till the video queue is empty */
	INF("%s", "Waiting for the video queue to be emptied");
	sem_getvalue(&s->sQueue, &queue_val);
	while(queue_val < s->iQueueSize - 1) {
		usleep(1000);
		sem_getvalue(&s->sQueue, &queue_val);
	}

	/* Let the renderer free the storage for the frames */
	INF("%s", "Telling the renderer to free it's buffers");
	for(i = 0; i < s->iQueueSize; ++i) {
		q = ll_next(s->lQueue);
		av_free(q->pFrame);
		s->rndr->video_queue_free(s->rndr_data, q->pFrame, &q->storage);
	}

	INF("%s", "Closing the renderer");
	s->rndr->video_close(s->rndr_data);
}

/* This function is called by the master clock when it is time to display the frame */
bool mp_stream_video_update(void* data) {
	mp_queue_t *q = data;

	pthread_rwlock_rdlock(&q->s->p->lSeek);
	if ((mp_stream_isrunning(q->s)) && (!q->s->p->bSeek))
		q->s->rndr->video_display	(q->s->rndr_data, &q->storage);
	else 	q->s->rndr->video_drop		(q->s->rndr_data, &q->storage);
	pthread_rwlock_unlock(&q->s->p->lSeek);
	sem_post(&q->s->sQueue);

	/* remove this event */
	return true;
}
